/****************************************************************************
*Implementacion de traductor de codigo de 3 direcciones a assembler         *
*Author     :   Simon Emmanuel Gutierrez Brida                              *
*Version    :   0.2.4                                                       *
*Fecha      :   Octubre/2011                                                *
*----------------------------------------------------                       *
*****************************************************************************/

#include "traductor.h"

/**Constructores*/

Traductor* nuevoTraductor(Codigo3D *input, FILE *output, char *mainFun) {
    Traductor *nuevo = (Traductor*) malloc(sizeof(Traductor));
    nuevo->codigo3d = input;
    nuevo->outputFile = output;
    nuevo->versionAsm = "PSEUDO";
    nuevo->mainFunc = mainFun;
    return nuevo;
}

/**Acceso a informacion*/

Codigo3D* obtenerCodigo3D(Traductor *traductor) {
    return traductor->codigo3d;    
}

FILE* obtenerArchivoOutput(Traductor *traductor) {
    return traductor->outputFile;
}

char* obtenerVersionAssembler(Traductor *traductor) {
    return traductor->versionAsm;
}

char* obtenerMainFunc(Traductor *traductor) {
    return traductor->mainFunc;
}

/**Modificacion atributos*/

void definirInput(Traductor *traductor, Codigo3D *input) {
    traductor->codigo3d = input;
}

void definirOutput(Traductor *traductor, FILE *output) {
    traductor->outputFile = output;    
}

void definirVersionAssembler(Traductor *traductor, char *versionAsm) {
    traductor->versionAsm = versionAsm;
}

void definirMainFunc(Traductor *traductor, char *mainFun) {
    traductor->mainFunc = mainFun;
}

/**Traduccion*/

Variable* obtenerVariableResultado(Com3D *com) {
    if (obtenerComando(com) == LOAD_CONST || obtenerComando(com) == LOAD_MEM) {
        return obtenerVariable_com3D(com,1);
    } else if (obtenerComando(com) == STORE_CONST || obtenerComando(com) == STORE_MEM) {
        return obtenerVariable_com3D(com,2);
    } else if (obtenerComando(com) == COM_MINUS || obtenerComando(com) == COM_NOT) {
        return obtenerVariable_com3D(com,2);
    } else if (obtenerComando(com) == COM_ADD || obtenerComando(com) == COM_SUB || obtenerComando(com) == COM_MULT || obtenerComando(com) == COM_DIV || obtenerComando(com) == COM_MOD) {
        return obtenerVariable_com3D(com,3);
    } else if (obtenerComando(com) == COM_GT || obtenerComando(com) == COM_LT || obtenerComando(com) == COM_EQ) {
        return obtenerVariable_com3D(com,3);
    } else if (obtenerComando(com) == COM_AND || obtenerComando(com) == COM_OR) {
        return obtenerVariable_com3D(com,3);
    } else if (obtenerComando(com) == GOTOLABEL_COND) {
        return obtenerVariable_com3D(com,1);
    } else if (obtenerComando(com) == COM_RETURN) {
        return obtenerVariable_com3D(com,2);
    } else if (obtenerComando(com) == COM_COHER_FLOAT || obtenerComando(com) == COM_COHER_INT) {
        return obtenerVariable_com3D(com,2);
    } else {
        return NULL;
    }
}

bool variableEsUtilizada(Com3D *com, Variable *var) {
    if (obtenerComando(com) == LOAD_MEM) {
        return obtenerVariable_com3D(com,2) == var && obtenerVariable_com3D(com,1) != var;
    } else if (obtenerComando(com) == STORE_MEM) {
        return obtenerVariable_com3D(com,1) == var && obtenerVariable_com3D(com,2) != var;
    } else if (obtenerComando(com) == COM_MINUS || obtenerComando(com) == COM_NOT) {
        return obtenerVariable_com3D(com,1) == var && obtenerVariable_com3D(com,2) != var;
    } else if (obtenerComando(com) == COM_ADD || obtenerComando(com) == COM_SUB || obtenerComando(com) == COM_MULT || obtenerComando(com) == COM_DIV || obtenerComando(com) == COM_MOD) {
        return (obtenerVariable_com3D(com,1) == var || obtenerVariable_com3D(com,2) == var) && obtenerVariable_com3D(com,3) != var;
    } else if (obtenerComando(com) == COM_GT || obtenerComando(com) == COM_LT || obtenerComando(com) == COM_EQ) {
        return (obtenerVariable_com3D(com,1) == var || obtenerVariable_com3D(com,2) == var) && obtenerVariable_com3D(com,3) != var;
    } else if (obtenerComando(com) == COM_AND || obtenerComando(com) == COM_OR) {
        return (obtenerVariable_com3D(com,1) == var || obtenerVariable_com3D(com,2) == var) && obtenerVariable_com3D(com,3) != var;
    } else if (obtenerComando(com) == GOTOLABEL_COND) {
        return obtenerVariable_com3D(com,1) == var;
    } else if (obtenerComando(com) == COM_RETURN) {
        return obtenerVariable_com3D(com,2) == var;
    } else if (obtenerComando(com) == COM_COHER_FLOAT || obtenerComando(com) == COM_COHER_INT) {
        return obtenerVariable_com3D(com,1) == var;
    } else {
        return false;
    }
}

Codigo3D* eliminarOperacionesInnecesarias(Codigo3D* original) {
    int cantComandos = cantidadComandos(original);
    int com;
    int com2;
    bool cleanVar = false;
    for (com = 0; com < cantComandos && (!cleanVar); com++) {
        Com3D *comandoActual = obtener_comando(original, com);
        Variable *variableActual = obtenerVariableResultado(comandoActual);
        bool varIsUsed = false;
        if (variableActual != NULL) {
            for (com2 = com + 1; com2 < cantComandos && (! varIsUsed);com2++) {
                Com3D *comandoActual2 = obtener_comando(original, com2);
                if (variableEsUtilizada(comandoActual2, variableActual)) {
                    varIsUsed = true;
                }
            }
        }
        cleanVar = !varIsUsed;
        if (cleanVar) {
            for (com = 0; com < cantComandos; com++) {
                Com3D *comandoActual = obtener_comando(original, com);
                if (obtenerVariableResultado(comandoActual) == variableActual) {
                    eliminar_comando(original,com);
                    com--;
                }
            }
            return eliminarOperacionesInnecesarias(original);
        }
    }
    return original;
}

Codigo3D* optimizarInput(Traductor *traductor) {
    return traductor->codigo3d;    
}


int obtenerIndiceVersion(char *version)  {
    int res = -1;
    bool found = false;
    int i = 0;
    while (i < (sizeof(versionNames)/sizeof(int)) && !found) {
        if (strcmp(version,versionNames[i]) == 0) {
            found = true;
            res = i;
        } else {
            i++;
        }
    }
    return res;
}

bool operacionReal(Com3D *comando) {
    bool real = false;
    int param;
    for (param = 1; param <= 3; param++) {
        if (esVariable(comando,param)) {
            real |= obtenerTipo(obtenerVariable_com3D(comando,param)) == TFLOAT;
        }
    }
    return real;
    /*if (esReal(comando, 1) || esReal(comando, 2) || esReal(comando, 3) ) {
        return true;
    } else {
        return false;
    }*/
}

int ajustarComando(Com3D *comando, int versionAsm) {
    int comandoOriginal = obtenerComando(comando);
    int comandoAjustado = comandoOriginal;
    bool ajustar = operacionReal(comando);
    if (versionAsm == 0) {
        return obtenerComando(comando);
    } else if (versionAsm == 1) {
        if (((comandoOriginal >= COM_MINUS) && (comandoOriginal <= COM_DIV)) && ajustar) {
                comandoAjustado += 15 + 1 + 2;
                return comandoAjustado;
        } else if (((comandoOriginal >= COM_GT) && (comandoOriginal <= COM_EQ)) && ajustar) {
                comandoAjustado += 9 + 5 + 1 + 2;
                return comandoAjustado;
        } else if ((comandoOriginal == COM_RETURN) && ajustar) {
                comandoAjustado += 8 + 1 + 2;
                return comandoAjustado;
        } else {
            return comandoAjustado;
        }
    }
}

char* ajustarDireccionMem(int offset, int versionAsm) {
    char *resPtr;
    char res[20];
    if (versionAsm == 0) {
        sprintf(res, "%d", offset);
        resPtr = res;
        return resPtr;
    } else if (versionAsm == 1) {
        sprintf(res, "%d(%%ebp)", offset);
        resPtr = res;
        return resPtr;
    }
}

char* agregarLabel(const char *rawInput, char *value1, char *value2, char *value3, int values, int operation, int asmVersion) {
    static char *labelLogic1 = "LABEL1_%d";
    static char *labelLogic2 = "LABEL2_%d";
    static int labelLogicCount = 0;
    int value1len = 0;
    int value2len = 0;
    int value3len = 0;
    if (values >= 1) {
        value1len = strlen(value1);
    }
    if (values >= 2) {
        value2len = strlen(value2);
    }
    if (values == 3) {
        value3len = strlen(value3);
    }
    char *result = (char*) malloc(sizeof(char) * (strlen(rawInput) + value1len + value2len + value3len + 2*7 + 3));
    char *resPtr;
    if (asmVersion == 0) {
        if (values == 1) {
            sprintf(result, rawInput,value1);
        } else if (values == 2) {
            sprintf(result, rawInput,value1,value2);
        } else if (values == 3) {
            sprintf(result, rawInput,value1,value2,value3);
        }
        resPtr = result;
        return resPtr;
    } else if (asmVersion == 1) {
        if (operation == COM_AND || operation == COM_OR || operation == COM_NOT) {
            char label1[15];
            char label2[15];
            sprintf(label1, labelLogic1, labelLogicCount);
            sprintf(label2, labelLogic2, labelLogicCount);
            labelLogicCount++;
            if (values == 1) {
                sprintf(result, rawInput, value1, label1, label2);
            } else if (values == 2) {
                sprintf(result, rawInput, value1, value2, label1, label2);
            } else if (values == 3) {
                sprintf(result, rawInput, value1, value2, value3, label1, label2);
            }
            resPtr = result;
            return resPtr;
        } else {
            return agregarLabel(rawInput,value1,value2,value3,values,operation,0);        
        }
    }
    
}

char* generateIntro(const char *rawInput, char *mainFun, int assmVer) {
    char *ptr;
    int extraSpace = 0;
    if (assmVer == 1) {
        extraSpace = strlen(mainFun);
    }
    char *res = (char*) malloc(sizeof(char)* (strlen(rawInput)+extraSpace));
    if (assmVer == 0) {
        sprintf(res, "%s",rawInput);
        ptr = res;
        return ptr;
    } else if (assmVer == 1) {
        Variable *aux = nuevaVariable_id("aux",true);
        int maxOffset = obtenerOffset(aux);
        maxOffset += 4;
        maxOffset *= -1;
        sprintf(res, rawInput, mainFun, maxOffset);
        ptr = res;
        return ptr;
    }
}

void traducir(Traductor *traductor) {
    int cantComandos = cantidadComandos(obtenerCodigo3D(traductor));
    int tablaTraduccion = obtenerIndiceVersion(obtenerVersionAssembler(traductor));
    int com;
    int param;
    if (tablaTraduccion >= 0) {
        char *intro = generateIntro(introAuxiliarCode[tablaTraduccion],obtenerMainFunc(traductor),tablaTraduccion);
        fprintf(obtenerArchivoOutput(traductor), "%s", intro);
        for (com = 0; com < cantComandos; com++) {
            Com3D *comandoActual = obtener_comando(obtenerCodigo3D(traductor), com);
            int valores = 3;
            char value[3][40];
            for (param = 1; param <= 3; param++) {
                if (esEntero(comandoActual,param)) {
                    sprintf(value[param-1], "%d", obtenerEntero(comandoActual,param));
                } else if (esReal(comandoActual,param)) {
                    sprintf(value[param-1], "%f", obtenerReal(comandoActual,param));
                } else if (esLabel(comandoActual,param)) {
                    sprintf(value[param-1], "%s", obtenerLabel(comandoActual,param));
                } else if (esVariable(comandoActual,param)) {
                    sprintf(value[param-1], "%s", ajustarDireccionMem(obtenerOffset(obtenerVariable_com3D(comandoActual,param)), tablaTraduccion)); //CAMBIAR A OFFSET
                } else if (esBool(comandoActual,param)) {
                    sprintf(value[param-1], "%d", obtenerBool(comandoActual,param));
                } else if (esNull(comandoActual,param)) {
                    valores--;
                }
            }
            int comandoAjustado = ajustarComando(comandoActual,tablaTraduccion);
            fprintf(obtenerArchivoOutput(traductor), "%s", agregarLabel(tablaTrad[tablaTraduccion][comandoAjustado], value[0], value[1], value[2], valores, obtenerComando(comandoActual), tablaTraduccion));
        }
        fprintf(obtenerArchivoOutput(traductor), "%s", outroAuxiliarCode[tablaTraduccion]);
    } else {
        fprintf(stderr, "***ERROR traductor.c:traducir : version assembler %s no soportada\n", obtenerVersionAssembler(traductor));
    }
}